[Draft] Multi Print Head Capability#38
Conversation
Squashed feature branch: multi-head (toolchanger) nozzle assignment and filament-swap scheduling, plus supporting work. - Multi-head layer analysis (windows, nozzle assignments, color-first and spatial-variance optimization modes) and structured result plumbing - Per-nozzle mesh tagging in the 3D preview and a head-swap schedule - 3MF export for multi-extruder printers: identity filament_map (Manual mode), full per-extruder config arrays, flush matrix, filament_diameter, and custom_gcode_per_layer pauses at swap layers - Misc: ortho/perspective camera toggle, mixed-colour layer Z-fighting fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ad schedule tests Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
|
@vycdev Is this PR too large? |
Sorry I haven't really had a chance to look at this yet. From what I see at a first glance, you've done a lot of amazing work, so I look forward to testing this. I'm gonna try to review it in the upcoming days, but it should be fine even if it's big. Edit: If you think this is not ready for review yet, you can tell me, since I see this is still a draft. |
|
@vycdev I'll ping you when it's ready- probably in 1-2 days. I'm going through with a fine-tooth comb right now. |
|
I was thinking about improving or adding new algorithms for auto-paint, do you think that would collide with this pull request? It matters if this is either simply a separate algorithm, or if its another step in the pipeline and integrates nicely with current algorithms while allowing them to be modified or new ones to be added without affecting this feature. I was also wondering if number 4, Reducing Height Cliffs Between Columns, would've been better suited as a separate pull request of it it's deeply tied with the multi head printing feature. I'm also interested how you made the graphs, because they look great and I think they can be used in the docs. And I'm curious if your 3d printer swaps filament on the parked nozzles, aka the non active ones while it's printing. At least that's what I am assuming. |
It may, but not in a large way- the architecture of MPH is as a post-processor. It takes what auto-paint provides, and re-arranges things, creates windows, etc. The only real breaking thing would be if the data structures were fundamentally altered heavily.
I can pull that out- spatial variance optimization is a beast of its own.
I used python to make SVGs, and then converted them to pngs. Here's the script I used for spatial vairance: https://gist.github.com/Bjohnson131/b38f134125de0af37ef3537de6a125f1
It does. TBH I hadn't considered doing this at all. right now, the .3mf file inserts gcode 'pause' commands every time filaments need to be swapped out, and then the user is supposed to go and change all the filaments according to the schedule. I hadn't considered a 'hot-swap' design... I'm sure that would be a lot faster, and more convenient to many, but right now that's not how it works. Would 100% be a good followup feature though. |
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Strips the spatial-variance mode from the multi-head pipeline — algorithm file, SVG diagram, type fields, and all UI/dispatch wiring — leaving color-accuracy as the sole optimization path. The full implementation is preserved on the feature/spatial-variance branch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Covers computeColorOptimalAssignments, optimizeNozzleAssignments, buildMultiHeadSchedule edge cases, and patchedLayersToPlan boundary conditions. Renames two misidentified tests so they reflect the path they actually exercise. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
tests/multiHeadAnalysisColorFirst.test.ts contains an extra closing }); at line 659. This causes npm test, npm run build, and npm run lint to fail with TypeScript parse error TS1128. Please remove it so the new multi-head test module runs. |
|
Non-blocking follow-up: Smooth meshing is not safe for a layer that has been partitioned into multiple colours. Each colour mask is passed separately to generateSmoothMesh, so both sides of a shared colour boundary are independently smoothed inward. This leaves the visible gaps shown in the preview and is present in the generated geometry, not just its shading. I created a follow up issue for this #40
|
|
Multi-head analysis runs synchronously from the Build handler, so larger head/palette configurations freeze the UI. The Search depth selector currently has no effect: it is stored and rendered but never reaches the solver. Please move the multi-head analysis off the main thread and make Fast, Balanced, and Thorough control a real bounded or sampled search strategy. |
|
Multi-head Printing was added to DOC_NAV_GROUPS, but the matching source page, src/docs/multi-head-printing.md, is not included. The docs UI silently filters that unresolved slug out, so the feature has no in-app documentation despite the new diagrams and navigation entry. Please add the documentation page. |
|
Multi-head settings are not persisted to kromacut.autopaint.v1. After enabling Multi-head mode, changing Head count/Search depth, and reloading the app, the settings reset to their defaults (off, 4, Balanced). Please include multiHeadMode, multiHeadCount, and multiHeadSearchDepth in the existing storage load/save payload, with backwards-compatible defaults for older saved data. |
IMO this should be fixed before merging- I'll have some time tn to take a closer look. |
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
|
Hm, that doesn't look right, are you sure it's not also a problem with your printer/filament? It looks like the nozzle is digging too much into the print, and there's also lots of stringing |
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>




How Multi-Head Processing Works
Multi-head printing loads N filaments at once and prints them in parallel, so colour changes happen at scheduled pauses rather than after every single run. Kromacut's job is to figure out the best way to group layers, assign filaments to nozzles, and arrange colors so the fewest pauses produce the most accurate result.
There are four main problems it solves, in order.
Glossary
1. Grouping Layers into Windows
Instead of swapping filament for every color change, the printer batches N consecutive color regions into a "window" and handles them all with the same N loaded filaments. It only pauses at window boundaries.
Before windows can be defined, Kromacut builds a run stack — a list of contiguous layer blocks where each block uses one filament. Colors are mapped to print heights by luminance (brighter = higher), then the full layer sequence is expanded at the printer's actual layer height. Where the filament index changes between layers, a new run starts.
Under the hood: selecting which runs become windows isn't just "take the first N, then the next N." Kromacut runs a dynamic programming pass over all candidate windows to find the non-overlapping subset that maximises a cumulative quality score. Each window gets an
errorFactor— how well its N filaments cover the colours in that band — and the DP finds the combination of windows that maximises the total. Because windows are fixed-width (always N runs), the DP is O(n): the best previous non-overlapping window is always exactly N steps back, so there's no backtracking. The search also stops early once no remaining window improves the score by more than 0.01% — no point squeezing out tiny gains near the bottom of the pile. It will also end if no windows of >1 run long can be constructed.Runs that don't land in any selected window are unclaimed and print with whatever filament was last loaded.
2. Assigning Filaments to Nozzles
Once windows are chosen and arranged appropriately, each window needs a filament assigned to each of its N nozzle slots. The goal is to match the target colors as closely as possible while reusing filaments across windows to minimize swaps.
Under the hood: this is split into two stages.
Stage 1 — what goes in each slot. For every window, Kromacut tries every possible combination of K filaments across the N layers — all K^N of them — and scores each one. The score is a perceptual color difference (more on that below) weighted by how many pixels in the frame use each color, so dominant colors matter more than rare ones. The top-scoring combination becomes that window's assignment.
Stage 2 — linking windows together. The per-window assignments are then threaded across the full print with a second DP. The state at each step is the N-tuple of currently loaded filament IDs. Moving from one window to the next costs one swap per nozzle slot that changes filament; idle slots carry their previous filament for free. The DP finds the sequence of assignments that minimises total swaps across the whole print.
One extra detail: individual runs inside a window can override the window-level consensus if a per-run slot assignment scores better. This lets two adjacent color regions in the same window use slightly different arrangements without forcing a full nozzle change.
3. Scoring Color Accuracy
FDM filaments are semi-translucent, so the color you see on a print isn't just the top filament — it's a blend of everything light passes through on its way in and out. The order filaments are stacked in directly changes the perceived color.
Under the hood: Kromacut models light transmission with:
TDis the filament's transmission distance — the depth at which 10% of light still passes through. At one TD of thickness, 90% is absorbed; stack more layers and it drops fast. The perceived color at each depth is composited as:This is evaluated layer by layer from the outside in, so the outer layers dominate and deeper layers contribute less and less.
Scoring uses ΔE in CIE Lab space (the CIE76 formula) rather than comparing raw RGB values. Lab is designed to match human perception, so a ΔE of 1 means a just-noticeable difference regardless of which part of the color spectrum you're in. This matters because two assignments with similar RGB error can look very different to a viewer — Kromacut optimises for what the eye sees.
For frontlit prints, the effective TD is multiplied by 0.1, compressing penetration depth to match how surface lighting works in practice.
4. Reducing Height Cliffs Between Columns (Spatial Variance)
The basic idea: when adjacent pixel columns are printed at very different heights, the side of the taller column is exposed to light at an angle rather than face-on. This bleeds color sideways and degrades the image near edges. The fix is to keep neighbouring columns close in height by grouping similar colors into the same print phase.
Under the hood: colors are sorted by luminance and divided into M = ⌈K/N⌉ bands, one per phase. The starting assignment is simple:
band[i] = floor(colour_index / N)after sorting. This is a reasonable first guess but not necessarily optimal, so it's refined by a local search.The search evaluates swapping pairs of colours between adjacent bands. The quality metric is:
where
W(A,B) = 1 / (luminance_distance(A,B)² + 0.0001). The weight is high when two colours are close in luminance — those are the ones most likely to appear side-by-side in the image, so pulling them into the same band has the biggest impact on cliff height. Any swap that lowers the total is accepted; the search repeats until no improving swap exists.Once phases are assigned, the support layers within each bar follow a rotation formula:
support_index = phase × N + (column_position + phase) mod N. This spreads support layers across nozzle slots so no two adjacent columns in the same phase pull from the same head — keeping the number of distinct colours at any single layer level at exactly N, matching what the printer has loaded.